#include "proxy.h"

int start_proxy_server(struct config_s* pconfig, uv_connection_cb on_new_connection)
{
	int r;
	uv_tcp_t* server = &pconfig->proxy_sock;
	uv_tcp_init(gloop, server);

	struct sockaddr_in addr;
	uv_ip4_addr(pconfig->proxy_addr, pconfig->proxy_port, &addr);
	uv_tcp_bind(server, (const struct sockaddr*)&addr, 0);
	r = uv_listen((uv_stream_t*)server, MAX_CLIENTS, on_new_connection);
	if (r) {
		printf("listen %s:%d failed: %s\n", pconfig->proxy_addr, pconfig->proxy_port, uv_err_name(r));
		goto exit;
	}

	log_message(LOG_INFO, "HTTPS proxy server listening on %s:%d", pconfig->proxy_addr, pconfig->proxy_port);
	printf("HTTPS proxy server listening on %s:%d\n", pconfig->proxy_addr, pconfig->proxy_port);
	printf("(\"stop this proxy by press key: ctrl+c\")\n");

	return 0;

exit:
	uv_close((uv_handle_t*)server, NULL);
	return -1;
}

void exit_proxy_server(struct config_s* pconfig)
{
	uv_close((uv_handle_t*)&pconfig->proxy_sock, NULL);
	printf("HTTPS proxy server stopped \n");
}


/*
 * figure out where to connect and build a new request line. 
 * Finally connect to the remote server.
 */
int process_request(struct child* pchild, struct request_s* request)
{
	llhttp_t *parser = &pchild->llhttp.parser;
	request->major = llhttp_get_http_major(parser);
	request->minor = llhttp_get_http_minor(parser);
	request->method = llhttp_get_method(parser); //HTTP_CONNECT = 5
	pchild->conn.protocol.major = request->major;
	pchild->conn.protocol.minor = request->minor;

	struct conn_s* connptr = &pchild->conn;
	//only care connect and get
	switch (request->method) {
	case HTTP_CONNECT:
	case HTTP_GET:
		break;
	default:
		//BAD_REQUEST_ERROR
		log_message(LOG_ERR,
			"process_request: Bad Request on file descriptor %d",
			pchild->conn.client_socket);
		indicate_http_error(connptr, 400, "Bad Request",
			"detail", "Request has an invalid format",
			"url", pchild->llhttp.url, NULL);
		return -1;
	}

	char* url = pchild->llhttp.url;
	if (strncasecmp(url, "http://", 7) == 0) {
		//http GET: check URL
		char* skipped_type = strstr(url, "//") + 2;
		if (extract_url(skipped_type, HTTP_PORT, request) < 0) {
			indicate_http_error(connptr, 400, "Bad Request",
				"detail", "Could not parse URL",
				"url", url, NULL);
			return -1;
		}
	} else if (request->method == HTTP_CONNECT) {
		//http CONNECT
		if (extract_url(url, HTTP_PORT_SSL, request) < 0) {
			indicate_http_error(connptr, 400, "Bad Request",
				"detail", "Could not parse URL",
				"url", url, NULL);
			return -1;
		}
	} else {
		indicate_http_error(connptr, 501, "Not Implemented",
			"detail",
			"Unknown method or unsupported protocol.",
			"url", url, NULL);
		log_message(LOG_INFO, "Unknown method (%s) or protocol (%s)",
			request->method, url);
		return -1;
	}

	return 0;
}

void on_resolved(uv_getaddrinfo_t* resolver, int status, struct addrinfo* res)
{
	struct child* pchild = (struct child*)uv_handle_get_data((const uv_handle_t*)resolver);
	struct conn_s* connptr = &pchild->conn;
	if (status < 0) {
		fprintf(stderr, "%s getaddrinfo callback error %s\n", 
			pchild->llhttp.url, uv_err_name(status));
		return;
	}

	char addr[17] = { '\0' };
	uv_ip4_name((struct sockaddr_in*)res->ai_addr, addr, 16);
	//fprintf(stderr, "%s\n", addr);
	uv_tcp_t* s = &connptr->server_socket;
	uv_tcp_init(gloop, s);

	uv_connect_t* connect = &connptr->uv_con;
	int port = connptr->port;

	struct sockaddr_in dest;
	uv_ip4_addr(addr, port, &dest);
	uv_handle_set_data((uv_handle_t*)connect, pchild);
	uv_tcp_connect(connect, s, (const struct sockaddr*)&dest, on_connect_remote_server);

	uv_freeaddrinfo(res);
}


/*
 * Open a connection to a remote host.  It's been re-written to use
 * the getaddrinfo() library function, which allows for a protocol
 * independent implementation (mostly for IPv4 and IPv6 addresses.)
 */
int opensock(struct child* pchild, const char* host, int port)
{
	uv_getaddrinfo_t* presolver = &pchild->conn.resolver;
	struct addrinfo hints;
	memset(&hints, 0, sizeof(hints));
	hints.ai_family = PF_INET;
	hints.ai_socktype = SOCK_STREAM;
	hints.ai_protocol = IPPROTO_TCP;
	hints.ai_flags = 0;

	pchild->conn.port = port;
	uv_handle_set_data((uv_handle_t*)presolver, pchild);
	int r = uv_getaddrinfo(gloop, presolver, on_resolved, host, NULL, &hints);
	if (r) {
		fprintf(stderr, "getaddrinfo call error %s\n", uv_err_name(r));
		return 1;
	}

	return 0;
}
